package com.cxx.purchasecharge.core.config;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;

import org.apache.log4j.Logger;

import com.cxx.purchasecharge.core.crypto.AESEncryption;
import com.cxx.purchasecharge.core.crypto.CryptoException;
import com.cxx.purchasecharge.core.crypto.CryptoFactory;
import com.cxx.purchasecharge.core.crypto.ICrypto;
import com.cxx.purchasecharge.core.util.CoreConstants;
import com.cxx.purchasecharge.core.util.StringUtil;

public class CxxPropertiesImpl implements ICxxProperties
{
    private static final Logger logger = Logger.getLogger(CxxPropertiesImpl.class);
    
    // The following is used to tag a property value that it is encrypted.
    // The format should be propertyName=!encrypted!ENCRYPTEDDATA
    private static final String ENCRYPTED_KEY = "!encrypted!";     
    
    private static ICrypto s_crypto;
    // These groups use the default keys, need to bootstrap using encrypted value
    // to read the keystore group.
    private static Set<String> s_specialGroups = new HashSet<String>();
    private static AESEncryption s_specialCrypto = null;
 
    static
    {
        try
        {
            s_specialGroups.add ("cryptokeys");
            s_specialCrypto = new AESEncryption();
            
            s_crypto = (ICrypto)CryptoFactory.getInstance();
        }
        catch (CryptoException ce)
        {
            logger.warn("Unable to retrieve a Crypto implementation", ce);
        }
    }
    
    // Private methods

    /**
     * Returns a valid path for the specified group
     * 
     * @param group
     * @return filePath
     * @throws ConfigException
     */
    private String getValidPropertiesFilepath(String group)
            throws ConfigException
    {
        return makeFullPath(BaseConfigUtil.getCxxPropertiesDir (), group);
    }
    
    /**
     * Returns the fully qualified property file name.
     * 
     * @param dirPath
     * @param group
     * @return the fully qualified property file name.
     * @throws ConfigException
     */
    private String makeFullPath(String dirPath, String group) throws ConfigException
    {
        StringBuilder fullPath = new StringBuilder();
        fullPath.append (dirPath);
        fullPath.append (File.separator);
        fullPath.append(group);
        fullPath.append(CoreConstants.PROPERTIES_EXT);
        
        File f = new File(fullPath.toString());
        if (f.exists())
        {
            return fullPath.toString();
        } 
        else
        {
            throw new ConfigException("Properties file doesn't exist at "
                    + fullPath.toString());
        }
    }

    /**
     * Returns the CxxProperties for the group using the specified filepath.
     * 
     * @param filepath
     * @param groupName
     * @return CxxProperties
     * @throws ConfigException
     * @throws IllegalArgumentException if filepath or groupName is null or empty
     */
    private CxxProperties getPropertiesUsingFile(String filepath,
            String groupName) throws ConfigException
    {
        if (StringUtil.isNullOrBlank(filepath))
        {
            throw new IllegalArgumentException(
                    "filepath can't be null or empty string");
        }
        if (StringUtil.isNullOrBlank(groupName))
        {
            throw new IllegalArgumentException(
                    "groupName can't be null or empty string");
        }

        FileInputStream input = null;
        Properties prop = null;

        try
        {
            File f = new File(filepath);
            input = new FileInputStream(f);
            prop = new Properties();
            prop.load(input);

            CxxProperties cfgProp = new CxxProperties(groupName);
            
            for (Entry <Object, Object> entry : prop.entrySet ())
            {
                String key = (String)entry.getKey ();
                String value = (String)entry.getValue ();
                
                if ( value != null && value.length() > 0)
                {
                    if ( s_specialGroups.contains (groupName.toLowerCase ()) && value.startsWith (ENCRYPTED_KEY))
                    {
                        value = value.trim();
                        
                        // Decrypt the value
                        if ( s_specialCrypto != null)
                        {
                            try
                            {
                                value = s_specialCrypto.decrypt(value.substring(ENCRYPTED_KEY.length()));
                            }
                            catch (CryptoException ce)
                            {
                                logger.warn("Unable to decrypt value ("+value+") in "+groupName+" just returning the original value.", ce);
                            } 
                        }
                    }
                    else if ( value.startsWith(ENCRYPTED_KEY))
                    {
                        value = value.trim();
                        
                        // Decrypt the value
                        try
                        {
                            // Replace with the decrypted value
                            value = s_crypto.decrypt(value.substring(ENCRYPTED_KEY.length()));
                        }
                        catch (CryptoException ce)
                        {
                            logger.warn("Unable to decrypt value ("+value+") in "+groupName+" just returning the original value.", ce);
                        }
                    }
                }
                
                cfgProp.put (key, value);  
            }

            return cfgProp;
        } 
        catch (FileNotFoundException fe)
        {
            throw new ConfigException("Unable to find the properties for groupName=" + groupName
                            + " file=" + filepath, fe);
        } 
        catch (IOException ie)
        {
            throw new ConfigException("Unable to read the properties for groupName=" + groupName
                            + " file=" + filepath, ie);
        } 
        finally
        {
            if (input != null)
            {
                try
                {
                    input.close();
                } catch (Exception e)
                {
                }
            }
        }
    }

    // Public methods

    /*
     * (non-Javadoc)
     * 
     * @see net.carefx.config.ICfgProperties#getProperties(java.lang.String)
     */
    public CxxProperties getProperties(String groupName) throws ConfigException
    {
        if (StringUtil.isNullOrBlank(groupName))
        {
            throw new IllegalArgumentException(
                    "groupName can't be null or empty string");
        }

        String filepath = getValidPropertiesFilepath(groupName);
        CxxProperties prop = getPropertiesUsingFile(filepath, groupName);
        return prop;
    }

    /* (non-Javadoc)
     * @see net.carefx.config.ICfgProperties#getProperties(java.lang.String, java.lang.String)
     */
    public CxxProperties getProperties (String filePath, String groupName) throws ConfigException
    {
        if (StringUtil.isNullOrBlank(filePath))
        {
            throw new IllegalArgumentException(
                    "filePath can't be null or empty string");
        }
        
        if (StringUtil.isNullOrBlank(groupName))
        {
            throw new IllegalArgumentException(
                    "groupName can't be null or empty string");
        }
        
        CxxProperties prop = getPropertiesUsingFile(makeFullPath(filePath,groupName), groupName);
        return prop;
    }

    /*
     * (non-Javadoc)
     * 
     * @see net.carefx.config.ICfgProperties#getProperties(java.util.Collection)
     */
    public Collection<CxxProperties> getProperties(Collection<String> groupNames)
            throws ConfigException
    {
        if (groupNames == null || groupNames.size() == 0)
        {
            throw new IllegalArgumentException(
                    "groupNames can't be null or empty");
        }

        Collection<CxxProperties> props = new ArrayList<CxxProperties>();
        for (String group : groupNames)
        {
            String filepath = getValidPropertiesFilepath(group);
            CxxProperties prop = getPropertiesUsingFile(filepath, group);

            props.add(prop);
        }

        return props;
    }

    
    /* (non-Javadoc)
     * @see net.carefx.config.ICfgProperties#getProperties(java.lang.String, java.util.Collection)
     */
    public Collection <CxxProperties> getProperties (String filePath, Collection <String> groupNames)
                                                                                                     throws ConfigException
    {
        if (StringUtil.isNullOrBlank(filePath))
        {
            throw new IllegalArgumentException(
                    "filePath can't be null or empty string");
        }
        
        if (groupNames == null || groupNames.size() == 0)
        {
            throw new IllegalArgumentException(
                    "groupNames can't be null or empty");
        }

        Collection<CxxProperties> props = new ArrayList<CxxProperties>();
        for (String group : groupNames)
        {
            CxxProperties prop = getPropertiesUsingFile(makeFullPath(filePath,group), group);

            props.add(prop);
        }

        return props;
    }


    
    
    /*
     * (non-Javadoc)
     * 
     * @see
     * net.carefx.config.ICfgProperties#getPropertiesMap(java.util.Collection)
     */
    public GroupMap getPropertiesMap(Collection<String> groupNames)
            throws ConfigException
    {
        if (groupNames == null || groupNames.size() == 0)
        {
            throw new IllegalArgumentException(
                    "groupNames can't be null or empty");
        }

        Collection<String> newGroupNames = FileSystemUtil.updateGroupNameWithWildCard(groupNames, CoreConstants.PROPERTIES_EXT);
        
        GroupMap map = new GroupMap();
        for (String group : newGroupNames)
        {
            String filepath = getValidPropertiesFilepath(group);
            CxxProperties prop = getPropertiesUsingFile(filepath, group);

            map.setProperties(group, prop);
        }

        return map;
    }

    /* (non-Javadoc)
     * @see net.carefx.config.ICfgProperties#getPropertiesMap(java.lang.String, java.util.Collection)
     */
    public GroupMap getPropertiesMap (String filePath, Collection <String> groupNames) throws ConfigException
    {
        if (StringUtil.isNullOrBlank(filePath))
        {
            throw new IllegalArgumentException(
                    "filePath can't be null or empty string");
        }
        
        if (groupNames == null || groupNames.size() == 0)
        {
            throw new IllegalArgumentException(
                    "groupNames can't be null or empty");
        }

        Collection<String> newGroupNames = FileSystemUtil.updateGroupNameWithWildCard(filePath, groupNames, CoreConstants.PROPERTIES_EXT);
        
        GroupMap map = new GroupMap();
        for (String group : newGroupNames)
        {
            CxxProperties prop = getPropertiesUsingFile(makeFullPath(filePath,group), group);

            map.setProperties(group, prop);
        }

        return map;
    }
    
}
